#include "StdAfx.h"
#include "Entity.h"

using namespace std;

namespace FEngine
{
	Entity::Entity(const string& _fileName, const string& _textureName)
		: Node()
	{
		mVertexBuffer		= NULL;
		mVertexDeclaration	= NULL;
		mIndexBuffer		= NULL;

		mMeshFileName		= _fileName;
		mMeshTexture		= NULL;
		mIsTexturePreLoaded	= false;

		mBaseAABB			= NULL;
		
		_createGeometry();
		_loadTexture(_textureName);
	}

	Entity::Entity(const string& _fileName, LPDIRECT3DTEXTURE9 _texture)
		: Node()
	{
		mVertexBuffer		= NULL;
		mVertexDeclaration	= NULL;
		mIndexBuffer		= NULL;

		mMeshFileName		= _fileName;
		mMeshTexture		= _texture;
		mIsTexturePreLoaded	= true;

		mBaseAABB			= NULL;
		
		_createGeometry();
	}

	Entity::~Entity()
	{
		if (mVertexBuffer)
			mVertexBuffer->Release();
		if (mIndexBuffer)
			mIndexBuffer->Release();
		if (mVertexDeclaration)
			mVertexDeclaration->Release();
		if (!mIsTexturePreLoaded && mMeshTexture)
			mMeshTexture->Release();
		if (mBaseAABB)
			delete mBaseAABB;
	}

	void Entity::_createGeometry()
	{
		IDirect3DDevice9* device = CoreEngine::getInstance()->getDeviceInterface();
		ID3DXMesh* mesh = NULL;

		// Load the mesh from the .x file
		std::wstring stemp = std::wstring(mMeshFileName.begin(), mMeshFileName.end());
		LPCWSTR fileName = stemp.c_str();
		if (D3DXLoadMeshFromX(fileName, D3DXMESH_MANAGED, device, NULL, NULL, NULL, NULL, &mesh) != D3D_OK)
			throw FEngineException("Entity", "_createGeometry", "Cannot load the file");

		// If there are no normals, compute them
		if(!(mesh->GetFVF() & D3DFVF_NORMAL))
		{
			ID3DXMesh* tempMesh;
			if (mesh->CloneMeshFVF(mesh->GetOptions(), mesh->GetFVF() | D3DFVF_NORMAL, device, &tempMesh) != D3D_OK)
				throw FEngineException("Entity", "_createGeometry", "Error while cloning the mesh (normal)");
			D3DXComputeNormals(tempMesh, NULL);
			mesh->Release();
			mesh = tempMesh;
		}

		// Get the adjacency data to optimise the mesh for the vertex cache
		DWORD* adjacencyData = new DWORD[mesh->GetNumFaces() * 3];
		if(adjacencyData == NULL)
			throw FEngineException("Entity", "_createGeometry", "Error while creating the adjacency data (adjacency)");
		mesh->ConvertPointRepsToAdjacency(NULL, adjacencyData);
		mesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE, adjacencyData, NULL, NULL, NULL);
		delete []adjacencyData;

		// Copy the mesh in a new one who use our vertex declaration
		ID3DXMesh* newMesh = NULL;
		if (mesh->CloneMesh(mesh->GetOptions(), VertexDeclaration, device, &newMesh) != D3D_OK)
			throw FEngineException("Entity", "_createGeometry", "Error while cloning the mesh (vertex declaration)");
		mesh->Release();
		mesh = newMesh;

		// Update the tangent and binormal info
		adjacencyData = new DWORD[mesh->GetNumFaces() * 3];
		if (adjacencyData == NULL)
			throw FEngineException("Entity", "_createGeometry", "Error while creating the adjacency data (tangent and binormal)");
		mesh->ConvertPointRepsToAdjacency(NULL, adjacencyData);
		D3DXComputeTangent(mesh, 0, 0, 0, 0, adjacencyData);
		delete []adjacencyData;

		// Create the vertex buffer
		mNumberVertices = mesh->GetNumVertices();
		int vertexBufferSize = mNumberVertices * D3DXGetDeclVertexSize(VertexDeclaration, 0);
		if (device->CreateVertexBuffer(vertexBufferSize, D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &mVertexBuffer, NULL) != D3D_OK)
			throw FEngineException("Entity", "_createGeometry", "Error while creating the vertex buffer");

		// Copy the vertex data directly into the memory
		LPVOID pVoidMeshVertices, pVoidMeshData;
		mVertexBuffer->Lock(0, 0, &pVoidMeshVertices, 0);
		mesh->LockVertexBuffer(0, &pVoidMeshData);
		memcpy(pVoidMeshVertices, pVoidMeshData, vertexBufferSize);
		
		// Get the AABB of the mesh
		FE_VERTEX* vertex = (FE_VERTEX*)(pVoidMeshData);
		Vector3 pos;
		float minX = FLT_MAX;
		float maxX = -FLT_MAX;
		float minY = FLT_MAX;
		float maxY = -FLT_MAX;
		float minZ = FLT_MAX;
		float maxZ = -FLT_MAX;
		for (int i = 0; i < mNumberVertices; ++i)
		{
			pos = vertex[i].POSITION;
			if (pos.x < minX)
				minX = pos.x;
			if (pos.x > maxX)
				maxX = pos.x;
			if (pos.y < minY)
				minY = pos.y;
			if (pos.y > maxY)
				maxY = pos.y;
			if (pos.z < minZ)
				minZ = pos.z;
			if (pos.z > maxZ)
				maxZ = pos.z;
		}
		mBaseAABB = new AABB(Vector3(minX, minY, minZ), Vector3(maxX, maxY, maxZ));

		mVertexBuffer->Unlock();
		mesh->UnlockVertexBuffer();

		// Get the description of the index buffer
		LPDIRECT3DINDEXBUFFER9 tempIndexBuffer;
		D3DINDEXBUFFER_DESC ibDescription;
		mesh->GetIndexBuffer(&tempIndexBuffer);
		tempIndexBuffer->GetDesc(&ibDescription);
		tempIndexBuffer->Release();

		// Create the index buffer
		int meshIndicesNumber = ibDescription.Format == D3DFMT_INDEX16 ? (ibDescription.Size / 2) : (ibDescription.Size / 4);
		mNumberPrimitives = meshIndicesNumber / 3;
		int indexBufferSize = ibDescription.Format == D3DFMT_INDEX16 ? (meshIndicesNumber * 2) : (meshIndicesNumber * 4);
		device->CreateIndexBuffer(indexBufferSize, D3DUSAGE_WRITEONLY, ibDescription.Format, D3DPOOL_DEFAULT, &mIndexBuffer, NULL);

		// Copy the index data directly into the memory
		LPVOID pVoidIndexSource, pVoidIndexDest;
		mesh->LockIndexBuffer(D3DLOCK_READONLY, &pVoidIndexSource);
		mIndexBuffer->Lock(0, indexBufferSize, &pVoidIndexDest, 0);
		memcpy(pVoidIndexDest, pVoidIndexSource, indexBufferSize);
		mesh->UnlockIndexBuffer();
		mIndexBuffer->Unlock();

		mesh->Release();

		// Load the vertex declaration
		device->CreateVertexDeclaration(VertexDeclaration, &mVertexDeclaration);
	}

	void Entity::_loadTexture(const string& _textureName)
	{
		std::wstring stemp = std::wstring(_textureName.begin(), _textureName.end());
		LPCWSTR fileName = stemp.c_str();

		if (D3DXCreateTextureFromFileEx(CoreEngine::getInstance()->getDeviceInterface(), fileName, D3DX_DEFAULT, D3DX_DEFAULT, 
									D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, 
									NULL, NULL, &mMeshTexture) != D3D_OK)
			throw FEngineException("Entity", "render", "Cannot load the texture file");
	}
	
	AABB* Entity::getAABB() const
	{
		Vector3 minPoint = mBaseAABB->getMinPoint();
		minPoint.x *= getScale().x;
		minPoint.y *= getScale().y;
		minPoint.z *= getScale().z;
		Vector3 maxPoint = mBaseAABB->getMaxPoint();
		maxPoint.x *= getScale().x;
		maxPoint.y *= getScale().y;
		maxPoint.z *= getScale().z;
		
		return new AABB(minPoint, maxPoint);
	}
	
	void Entity::update(double _elapsedTime)
	{
		Node::update(_elapsedTime);
	}

	void Entity::render(double _elapsedTime)
	{
		Node::render(_elapsedTime);

		LPD3DXEFFECT shader = CoreEngine::getInstance()->getShaderEffect();
		IDirect3DDevice9* device = CoreEngine::getInstance()->getDeviceInterface();

		D3DXMATRIX spaceMat = _getWorldMatrix();
		D3DXMATRIX matWVP = spaceMat * CoreEngine::getInstance()->getCamera()->_getMatrixView() * CoreEngine::getInstance()->getCamera()->_getMatrixProjection();

		if (shader->SetTechnique(shader->GetTechniqueByName("Mesh")) != D3D_OK)
			throw FEngineException("Entity", "render", "Unknown shader technique");
		if (shader->SetMatrix(shader->GetParameterByName(NULL, "gWorldViewProj"), &matWVP) != D3D_OK)
			throw FEngineException("Entity", "render", "Unknown shader parameter");
		if (shader->SetTexture(shader->GetParameterByName(NULL, "gMeshTexture"), mMeshTexture) != D3D_OK)
			throw FEngineException("Entity", "render", "Unknown shader parameter");
		shader->CommitChanges();
		
		device->SetVertexDeclaration(mVertexDeclaration);
		device->SetStreamSource(0, mVertexBuffer, 0, sizeof(FE_VERTEX));
		device->SetIndices(mIndexBuffer);
		
		unsigned int cPasses, iPass;
		shader->Begin(&cPasses, 0);
		for (iPass= 0; iPass< cPasses; ++iPass)
		{
			shader->BeginPass(iPass);
			CoreEngine::getInstance()->getDeviceInterface()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, mNumberVertices, 0, mNumberPrimitives);
			shader->EndPass();
		}
		shader->End();
	}


} // namespace FEngine
